1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.graphics.g2d.geometrybatch; 12 import hip.graphics.orthocamera; 13 import hip.hiprenderer.renderer; 14 import hip.hiprenderer.shader; 15 import hip.error.handler; 16 import hip.graphics.mesh; 17 import hip.math.matrix; 18 import hip.math.utils; 19 import hip.math.vector; 20 public import hip.api.graphics.color; 21 public import hip.api.graphics.batch; 22 23 24 enum defaultColor = HipColor.white; 25 26 @HipShaderInputLayout struct HipGeometryBatchVertex 27 { 28 import hip.math.vector; 29 Vector3 vPosition; 30 // @HipShaderInputPadding float __padding = 0; 31 HipColor vColor = HipColor.white; 32 33 static enum floatCount = HipGeometryBatchVertex.sizeof / float.sizeof; 34 } 35 36 @HipShaderVertexUniform("Geom") 37 struct HipGeometryBatchVertexUniforms 38 { 39 Matrix4 uModel = Matrix4.identity; 40 Matrix4 uView = Matrix4.identity; 41 Matrix4 uProj = Matrix4.identity; 42 } 43 44 @HipShaderFragmentUniform("FragVars") 45 struct HipGeometryBatchFragmentUniforms 46 { 47 float[4] uGlobalColor = [1,1,1,1]; 48 } 49 50 /** 51 * This class uses the vertex layout XYZ RGBA. 52 * it is meant to be a 2D API for drawing primitives 53 */ 54 class GeometryBatch : IHipBatch 55 { 56 protected Mesh mesh; 57 protected index_t lastIndexDrawn; 58 protected index_t lastVertexDrawn; 59 protected index_t currentIndex; 60 protected index_t verticesCount; 61 protected index_t indicesCount; 62 protected HipColor currentColor; 63 64 float managedDepth = 0; 65 HipOrthoCamera camera; 66 HipGeometryBatchVertex[] vertices; 67 index_t[] indices; 68 69 70 this(HipOrthoCamera camera = null, index_t verticesCount=64_000, index_t indicesCount=64_000) 71 { 72 Shader s = HipRenderer.newShader(HipShaderPresets.GEOMETRY_BATCH); 73 s.addVarLayout(ShaderVariablesLayout.from!HipGeometryBatchVertexUniforms); 74 s.addVarLayout(ShaderVariablesLayout.from!HipGeometryBatchFragmentUniforms); 75 s.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD); 76 77 78 mesh = new Mesh(HipVertexArrayObject.getVAO!HipGeometryBatchVertex, s); 79 vertices = new HipGeometryBatchVertex[verticesCount]; 80 indices = new index_t[indicesCount]; 81 indices[] = 0; 82 //Initialize the mesh with 0 83 mesh.createVertexBuffer(verticesCount, HipBufferUsage.DYNAMIC); 84 mesh.createIndexBuffer(indicesCount, HipBufferUsage.DYNAMIC); 85 mesh.vao.bind(); 86 mesh.setIndices(indices); 87 mesh.setVertices(vertices); 88 mesh.sendAttributes(); 89 this.setColor(defaultColor); 90 91 if(camera is null) 92 camera = new HipOrthoCamera(); 93 this.camera = camera; 94 95 } 96 97 protected pragma(inline) void checkVerticesCount(int howMuch) 98 { 99 if(verticesCount+howMuch >= this.vertices.length/HipGeometryBatchVertex.floatCount) 100 { 101 import hip.util.string; 102 String s = String("Too many vertices ", verticesCount+howMuch, " for a buffer of size ", 103 this.vertices.length/HipGeometryBatchVertex.floatCount 104 ); 105 ErrorHandler.assertExit(false, s.toString); 106 } 107 } 108 109 void setCurrentDepth(float depth){managedDepth = depth;} 110 111 112 /** 113 * Adds a vertex to the structure and return its current index. 114 */ 115 index_t addVertex(float x, float y, float z) 116 { 117 if(currentColor.a == 0) return verticesCount; 118 vertices[verticesCount] = HipGeometryBatchVertex( 119 Vector3(x,y,z), 120 currentColor 121 ); 122 return verticesCount++; 123 } 124 125 void addIndex(index_t[] newIndices ...) 126 { 127 if(currentColor.a == 0) return; 128 if(currentIndex+newIndices.length >= this.indices.length) 129 { 130 import hip.util.string; 131 String s = String("Too many indices ", currentIndex+1, " for a buffer of size ", this.indices.length); 132 ErrorHandler.assertExit(false, s.toString); 133 } 134 foreach(index; newIndices) 135 indices[currentIndex++] = index; 136 } 137 void setColor(HipColor c) 138 { 139 assert(c != HipColor.no, "Can't use 'no' color on geometry batch"); 140 currentColor = c; 141 } 142 143 protected void triangleVertices(int x1, int y1, int x2, int y2, int x3, int y3) 144 { 145 checkVerticesCount(3); 146 addVertex(x1, y1, managedDepth); 147 addVertex(x2, y2, managedDepth); 148 addVertex(x3, y3, managedDepth); 149 addIndex( 150 cast(index_t)(verticesCount-3), 151 cast(index_t)(verticesCount-2), 152 cast(index_t)(verticesCount-1) 153 ); 154 } 155 156 157 protected void fillEllipseVertices(int x, int y, int radiusW, int radiusH, int degrees, int startDegrees ,int precision) 158 { 159 // assert(precision >= 3, "Can't have a circle with less than 3 vertices"); 160 161 //Normalize the precision for iterating it on the loop, 162 //Multiply by degrees * DEG_TO_RAD 163 float angle_mult = (1.0/precision) * (degrees) * (PI/180.0); 164 165 float startAngle = (PI/180.0) * startDegrees; 166 167 checkVerticesCount(2); 168 index_t centerIndex = addVertex(x, y, managedDepth); 169 //The first vertex 170 index_t lastVert = addVertex(x + radiusW*cos(startAngle), y + radiusH*sin(startAngle), managedDepth); 171 index_t firstVert = lastVert; 172 173 checkVerticesCount(precision); 174 for(int i = 0; i < precision; i++) 175 { 176 //Divide degrees for the total iterations 177 float nextAngle = (i+1)*angle_mult + startAngle; 178 179 //Use a temporary variable to hold the new lastVert for more performance 180 //on addIndex calls 181 index_t tempNewLastVert = addVertex(x+radiusW*cos(nextAngle), y + radiusH*sin(nextAngle), managedDepth); 182 183 addIndex( 184 centerIndex, //Puts the center first 185 lastVert, //Appends the vertex from the last iteration 186 tempNewLastVert//Appends the next vertex 187 ); 188 //Updates the last iteration with the next vertex 189 lastVert = tempNewLastVert; 190 } 191 192 addIndex( 193 centerIndex, 194 lastVert, 195 firstVert 196 ); 197 } 198 199 200 201 void drawEllipse(int x, int y, int radiusW, int radiusH, int degrees = 360, HipColor color = HipColor.no, int precision = 24) 202 { 203 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 204 if(HipRenderer.getMode != HipRendererMode.LINE) 205 { 206 flush(); 207 HipRenderer.setRendererMode(HipRendererMode.LINE); 208 } 209 float angle_mult = (1.0/precision) * degrees * (PI/180.0); 210 checkVerticesCount(1); 211 index_t currVert = addVertex(x+ radiusW*cos(0.0), y + radiusH*sin(0.0), managedDepth); 212 index_t firstVert = currVert; 213 214 checkVerticesCount(precision); 215 for(int i = 1; i < precision+1; i++) 216 { 217 float nextAngle = angle_mult * i; 218 index_t tempNextVert = addVertex(x + radiusW * cos(nextAngle), y + radiusH*sin(nextAngle), managedDepth); 219 220 addIndex(currVert, tempNextVert); 221 currVert = tempNextVert; 222 } 223 224 addIndex(firstVert, currVert); 225 setColor(oldColor); 226 } 227 228 private HipColor setColorIfChangedAndGetOldColor(in HipColor color) 229 { 230 HipColor oldColor = currentColor; 231 if(color != HipColor.no) 232 setColor(color); 233 return oldColor; 234 } 235 236 ///With this default precision, the circle should be smooth enough 237 void fillEllipse(int x, int y, int radiusW, int radiusH = -1, int degrees = 360, HipColor color = HipColor.no, int precision = 24) 238 { 239 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 240 if(radiusH == -1) 241 radiusH = radiusW; 242 if(HipRenderer.getMode != HipRendererMode.TRIANGLES) 243 { 244 flush(); 245 HipRenderer.setRendererMode(HipRendererMode.TRIANGLES); 246 } 247 fillEllipseVertices(x, y, radiusW, radiusH, degrees, 0, precision); 248 setColor(oldColor); 249 } 250 251 void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, HipColor color = HipColor.no) 252 { 253 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 254 if(HipRenderer.getMode != HipRendererMode.TRIANGLES) 255 { 256 flush(); 257 HipRenderer.setRendererMode(HipRendererMode.TRIANGLES); 258 } 259 triangleVertices(x1,y1,x2,y2,x3,y3); 260 setColor(oldColor); 261 } 262 void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, HipColor color = HipColor.no) 263 { 264 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 265 if(HipRenderer.getMode != HipRendererMode.LINE_STRIP) 266 { 267 flush(); 268 HipRenderer.setRendererMode(HipRendererMode.LINE_STRIP); 269 } 270 triangleVertices(x1, y1, x2, y2, x3, y3); 271 setColor(oldColor); 272 } 273 274 void drawLine(int x1, int y1, int x2, int y2, HipColor color = HipColor.no) 275 { 276 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 277 if(HipRenderer.getMode != HipRendererMode.LINE) 278 { 279 flush(); 280 HipRenderer.setRendererMode(HipRendererMode.LINE); 281 } 282 checkVerticesCount(2); 283 addVertex(x1, y1, managedDepth); 284 addVertex(x2, y2, managedDepth); 285 286 addIndex( 287 cast(index_t)(verticesCount-2), 288 cast(index_t)(verticesCount-1) 289 ); 290 setColor(oldColor); 291 } 292 293 void drawLine(float x1, float y1, float x2, float y2, HipColor color = HipColor.no) 294 { 295 drawLine( 296 cast(int)x1, 297 cast(int)y1, 298 cast(int)x2, 299 cast(int)y2, 300 color 301 ); 302 } 303 304 void drawQuadraticBezierLine(int x0, int y0, int x1, int y1, int x2, int y2, int precision=24, HipColor color = HipColor.no) 305 { 306 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 307 308 Vector2 last = Vector2(x0, y0); 309 310 float precisionMultiplier = 1.0f/precision; 311 312 for(int i = 0; i <= precision; i++) 313 { 314 float t = cast(float)i*precisionMultiplier; 315 float tNext = t+precisionMultiplier; 316 Vector2 bz = quadraticBezier(x0, y0, x1, y1, x2, y2, tNext); 317 drawLine(last.x, last.y, bz.x, bz.y); 318 last = bz; 319 } 320 drawLine(last.x, last.y, x2, y2); 321 setColor(oldColor); 322 } 323 324 void drawPixel(int x, int y, HipColor color = HipColor.no) 325 { 326 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 327 if(HipRenderer.getMode != HipRendererMode.POINT) 328 { 329 flush(); 330 HipRenderer.setRendererMode(HipRendererMode.POINT); 331 } 332 checkVerticesCount(1); 333 addVertex(x, y, managedDepth); 334 addIndex(verticesCount); 335 setColor(oldColor); 336 } 337 338 /** 339 * Draws the following rectangle scheme: 340 * 0 _______ 3 341 * | | 342 * | | 343 * |_______| 344 * 1 2 345 * 0, 1, 2 346 * 2, 3, 0 347 */ 348 pragma(inline, true) 349 protected void rectangleVertices(int x, int y, int w, int h) 350 { 351 checkVerticesCount(4); 352 index_t topLeft = addVertex(x, y, managedDepth); 353 index_t botLeft = addVertex(x, y+h, managedDepth); 354 index_t botRight= addVertex(x+w, y+h, managedDepth); 355 index_t topRight= addVertex(x+w, y, managedDepth); 356 357 addIndex( 358 topLeft, botLeft, botRight, 359 botRight, topRight, topLeft 360 ); 361 362 } 363 364 void drawRectangle(int x, int y, int w, int h, HipColor color = HipColor.no) 365 { 366 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 367 if(HipRenderer.getMode != HipRendererMode.LINE_STRIP) 368 { 369 flush(); 370 HipRenderer.setRendererMode(HipRendererMode.LINE_STRIP); 371 } 372 rectangleVertices(x,y,w,h); 373 setColor(oldColor); 374 } 375 376 void fillRoundRect(int x, int y, int w, int h, int radius = 4, HipColor color = HipColor.no, int vertices = 16) 377 { 378 if(radius == 0) 379 return fillRectangle(x,y,w,h,color); 380 if(HipRenderer.getMode != HipRendererMode.TRIANGLES) 381 { 382 flush(); 383 HipRenderer.setRendererMode(HipRendererMode.TRIANGLES); 384 } 385 int vPerEdge = vertices/4; 386 int r2 = radius*2; 387 HipColor old = setColorIfChangedAndGetOldColor(color); 388 389 ///Draw internal rect. 390 rectangleVertices(x+radius, y+radius, w-r2, h-r2); 391 392 ///Draw ellipses and also draw border rects 393 //Top Left 394 fillEllipseVertices(x+radius, y+radius, radius, radius, 90, 180, vPerEdge); 395 rectangleVertices(x+radius, y, w - r2, radius); 396 //Top Right 397 fillEllipseVertices(x+w-radius, y+radius, radius, radius, 90, 270, vPerEdge); 398 rectangleVertices(x+w-radius, y+radius, radius, h-r2); 399 // Bottom Right 400 fillEllipseVertices(x+w-radius, y+h-radius, radius, radius, 90, 0, vPerEdge); 401 rectangleVertices(x+radius, y+h-radius, w-r2, radius); 402 //Bottom Left 403 fillEllipseVertices(x+radius, y+h-radius, radius, radius, 90, 90, vPerEdge); 404 rectangleVertices(x, y+radius, radius, h-r2); 405 406 setColor(old); 407 } 408 409 410 void fillRectangle(int x, int y, int w, int h, HipColor color = HipColor.no) 411 { 412 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 413 if(HipRenderer.getMode != HipRendererMode.TRIANGLES) 414 { 415 flush(); 416 HipRenderer.setRendererMode(HipRendererMode.TRIANGLES); 417 } 418 rectangleVertices(x,y,w,h); 419 setColor(oldColor); 420 } 421 422 void draw() 423 { 424 const uint count = this.currentIndex; 425 import hip.console.log; 426 427 if(count - lastIndexDrawn != 0) 428 { 429 mesh.bind(); 430 mesh.updateVertices(cast(float[])vertices[lastVertexDrawn..verticesCount], lastVertexDrawn); 431 mesh.updateIndices(indices[lastIndexDrawn..currentIndex], lastIndexDrawn); 432 433 mesh.shader.setFragmentVar("FragVars.uGlobalColor", cast(float[4])[1,1,1,1], true); 434 mesh.shader.setVertexVar("Geom.uProj", camera.proj, true); 435 mesh.shader.setVertexVar("Geom.uModel", Matrix4.identity(), true); 436 mesh.shader.setVertexVar("Geom.uView", camera.view, true); 437 438 mesh.shader.sendVars(); 439 //Vertices to render = indices.length 440 this.mesh.draw(count - lastIndexDrawn, HipRenderer.getMode, lastIndexDrawn); 441 mesh.unbind(); 442 } 443 lastIndexDrawn = count; 444 lastVertexDrawn = verticesCount; 445 } 446 447 void flush() 448 { 449 draw(); 450 lastVertexDrawn = verticesCount = 0; 451 lastIndexDrawn = currentIndex = 0; 452 } 453 454 }